#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
认证管理模块
负责用户认证、权限控制、会话管理等功能
"""

from typing import Optional, Dict, Any, List
from datetime import datetime, timedelta
import hashlib
import secrets

from models import User
from utils import logger, input_helper
from exceptions import AuthenticationError, PermissionError, ValidationError


class Session:
    """会话类"""
    
    def __init__(self, user: User, session_id: str = None):
        self.user = user
        self.session_id = session_id or secrets.token_urlsafe(32)
        self.created_at = datetime.now()
        self.last_activity = datetime.now()
        self.is_active = True
    
    def update_activity(self):
        """更新最后活动时间"""
        self.last_activity = datetime.now()
    
    def is_expired(self, timeout_minutes: int = 30) -> bool:
        """检查会话是否过期"""
        if not self.is_active:
            return True
        
        timeout = timedelta(minutes=timeout_minutes)
        return datetime.now() - self.last_activity > timeout
    
    def extend_session(self, timeout_minutes: int = 30):
        """延长会话时间"""
        self.last_activity = datetime.now()
        self.is_active = True


class AuthManager:
    """认证管理器"""
    
    def __init__(self, data_manager):
        self.data_manager = data_manager
        self.current_session: Optional[Session] = None
        self.sessions: Dict[str, Session] = {}
        self.max_login_attempts = 5
        self.lockout_duration = timedelta(minutes=15)
        self.login_attempts: Dict[str, Dict[str, Any]] = {}
    
    def login(self, username: str, password: str) -> bool:
        """用户登录"""
        try:
            # 检查账户是否被锁定
            if self._is_account_locked(username):
                remaining_time = self._get_lockout_remaining_time(username)
                raise AuthenticationError(f"账户已被锁定，请 {remaining_time} 后再试")
            
            # 验证用户名和密码
            user = self.data_manager.get_user(username)
            if not user:
                self._record_login_attempt(username, False)
                raise AuthenticationError("用户名或密码错误")
            
            if not User.verify_password(password, user.password_hash):
                self._record_login_attempt(username, False)
                raise AuthenticationError("用户名或密码错误")
            
            # 登录成功
            self._clear_login_attempts(username)
            user.update_last_login()
            
            # 创建会话
            self.current_session = Session(user)
            self.sessions[self.current_session.session_id] = self.current_session
            
            logger.info(f"用户登录成功: {username} ({user.role})")
            return True
            
        except AuthenticationError:
            raise
        except Exception as e:
            logger.error(f"登录过程出错: {e}")
            raise AuthenticationError(f"登录失败: {e}")
    
    def logout(self) -> bool:
        """用户登出"""
        if not self.current_session:
            return False
        
        session_id = self.current_session.session_id
        if session_id in self.sessions:
            self.sessions[session_id].is_active = False
            del self.sessions[session_id]
        
        logger.info(f"用户登出: {self.current_session.user.username}")
        self.current_session = None
        return True
    
    def get_current_user(self) -> Optional[User]:
        """获取当前用户"""
        if self.current_session and not self.current_session.is_expired():
            self.current_session.update_activity()
            return self.current_session.user
        return None
    
    def require_permission(self, required_role: str):
        """权限检查装饰器"""
        def decorator(func):
            def wrapper(*args, **kwargs):
                if not self.current_session:
                    raise PermissionError("请先登录")
                
                if self.current_session.is_expired():
                    self.logout()
                    raise PermissionError("会话已过期，请重新登录")
                
                current_user = self.get_current_user()
                if not current_user:
                    raise PermissionError("用户信息获取失败")
                
                if current_user.role not in ['admin', required_role]:
                    raise PermissionError(f"权限不足，需要 {required_role} 或 admin 权限")
                
                return func(*args, **kwargs)
            return wrapper
        return decorator
    
    def require_login(self):
        """登录检查装饰器"""
        def decorator(func):
            def wrapper(*args, **kwargs):
                if not self.current_session or self.current_session.is_expired():
                    raise PermissionError("请先登录")
                return func(*args, **kwargs)
            return wrapper
        return decorator
    
    def create_user(self, username: str, password: str, role: str, student_id: str = "") -> User:
        """创建用户"""
        # 验证角色
        if role not in ['admin', 'teacher', 'student']:
            raise ValidationError("角色必须是 admin、teacher 或 student")
        
        # 验证学生角色必须提供学号
        if role == 'student' and not student_id:
            raise ValidationError("学生角色必须提供学号")
        
        # 验证学号是否存在
        if role == 'student' and student_id:
            if not self.data_manager.get_student(student_id):
                raise ValidationError(f"学号 {student_id} 不存在")
        
        # 创建用户
        user_data = {
            'username': username,
            'password_hash': User.hash_password(password),
            'role': role,
            'student_id': student_id
        }
        
        return self.data_manager.add_user(user_data)
    
    def change_password(self, old_password: str, new_password: str) -> bool:
        """修改密码"""
        if not self.current_session:
            raise PermissionError("请先登录")
        
        user = self.get_current_user()
        if not user:
            raise PermissionError("用户信息获取失败")
        
        # 验证旧密码
        if not User.verify_password(old_password, user.password_hash):
            raise AuthenticationError("旧密码错误")
        
        # 验证新密码
        if len(new_password) < 6:
            raise ValidationError("新密码长度不能少于6位")
        
        # 更新密码
        user.password_hash = User.hash_password(new_password)
        logger.info(f"用户修改密码成功: {user.username}")
        return True
    
    def update_user_info(self, **update_data) -> User:
        """更新用户信息"""
        if not self.current_session:
            raise PermissionError("请先登录")
        
        user = self.get_current_user()
        if not user:
            raise PermissionError("用户信息获取失败")
        
        # 只允许更新特定字段
        allowed_fields = ['student_id']
        for field, value in update_data.items():
            if field in allowed_fields and hasattr(user, field):
                setattr(user, field, value)
        
        logger.info(f"用户信息更新成功: {user.username}")
        return user
    
    def get_user_permissions(self) -> List[str]:
        """获取用户权限列表"""
        if not self.current_session:
            return []
        
        user = self.get_current_user()
        if not user:
            return []
        
        permissions = {
            'admin': [
                'user_management', 'student_management', 'course_management',
                'grade_management', 'data_analysis', 'system_config',
                'data_import_export', 'backup_restore'
            ],
            'teacher': [
                'student_query', 'course_management', 'grade_management',
                'data_analysis', 'data_import_export'
            ],
            'student': [
                'view_personal_info', 'view_grades', 'calculate_gpa'
            ]
        }
        
        return permissions.get(user.role, [])
    
    def _is_account_locked(self, username: str) -> bool:
        """检查账户是否被锁定"""
        if username not in self.login_attempts:
            return False
        
        attempts_info = self.login_attempts[username]
        if attempts_info['attempts'] >= self.max_login_attempts:
            lockout_time = attempts_info['last_attempt'] + self.lockout_duration
            return datetime.now() < lockout_time
        
        return False
    
    def _get_lockout_remaining_time(self, username: str) -> str:
        """获取锁定剩余时间"""
        if username not in self.login_attempts:
            return "0分钟"
        
        attempts_info = self.login_attempts[username]
        lockout_time = attempts_info['last_attempt'] + self.lockout_duration
        remaining = lockout_time - datetime.now()
        
        if remaining.total_seconds() <= 0:
            return "0分钟"
        
        minutes = int(remaining.total_seconds() / 60)
        return f"{minutes}分钟"
    
    def _record_login_attempt(self, username: str, success: bool):
        """记录登录尝试"""
        if success:
            if username in self.login_attempts:
                del self.login_attempts[username]
            return
        
        if username not in self.login_attempts:
            self.login_attempts[username] = {
                'attempts': 0,
                'last_attempt': None
            }
        
        self.login_attempts[username]['attempts'] += 1
        self.login_attempts[username]['last_attempt'] = datetime.now()
        
        logger.warning(f"登录失败: {username} (第{self.login_attempts[username]['attempts']}次)")
    
    def _clear_login_attempts(self, username: str):
        """清除登录尝试记录"""
        if username in self.login_attempts:
            del self.login_attempts[username]
    
    def cleanup_expired_sessions(self):
        """清理过期会话"""
        expired_sessions = []
        for session_id, session in self.sessions.items():
            if session.is_expired():
                expired_sessions.append(session_id)
        
        for session_id in expired_sessions:
            del self.sessions[session_id]
        
        if expired_sessions:
            logger.info(f"清理了 {len(expired_sessions)} 个过期会话")
    
    def get_session_info(self) -> Dict[str, Any]:
        """获取会话信息"""
        if not self.current_session:
            return {}
        
        return {
            'username': self.current_session.user.username,
            'role': self.current_session.user.role,
            'session_id': self.current_session.session_id,
            'created_at': self.current_session.created_at.isoformat(),
            'last_activity': self.current_session.last_activity.isoformat(),
            'is_active': self.current_session.is_active
        }
